package com.octopus.core;

import java.util.HashMap;
import java.util.Map;
import lombok.NonNull;

/**
 * 封装爬虫任务请求，在实际工作中通常每一条链接对应一个爬虫任务。 调度器会在适当的时机进行爬虫任务入列和出列操作。
 *
 * <p>爬虫引擎会使用下载器处理每一个出列的任务单元
 *
 * @author shoulai.yang@gmail.com
 * @date 2019/07/31
 */
public class Request {

  /** 默认下载重试次数 */
  private static final int DEFAULT_MAX_RETRY_TIMES = 3;

  /** 默认下载超时时间 */
  private static final int DEFAULT_TIMEOUT = 30000;

  /** HTTP GET */
  private static final String REQUEST_METHOD_GET = "GET";

  private static final String DEFAULT_USER_AGENT =
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36";

  /** 下载链接 */
  private String url;

  /** http 请求方法 */
  private String method = REQUEST_METHOD_GET;

  /** 预期响应内容编码 */
  private String charset;

  /**
   * 标记该任务是否可重复
   *
   * <p>具体任务能不能重复执行需要配合调度器使用
   *
   * @see com.octopus.core.scheduler.Scheduler#visited(Request)
   * @see com.octopus.core.scheduler.Scheduler#visit(Request)
   */
  private boolean repeatable = true;

  /** 最大重试次数 */
  private int maxRetryTimes = DEFAULT_MAX_RETRY_TIMES;

  /** 任务允许下载重试次数 */
  private int retryTimes = 0;

  /** http header */
  private Map<String, String> headers = new HashMap<>();

  /** http cookie */
  private Map<String, String> cookies = new HashMap<>();

  /** 下载超时时间 */
  private int readTimeout = DEFAULT_TIMEOUT;

  /** 下载链接超时时间 */
  private int connectTimeout = DEFAULT_TIMEOUT;

  /**
   * 额外信息存储
   *
   * <p>例如可以将列表页面的数据临时保存为额外信息，接着在详情页面解析拿出这部分信息进行数据整合
   *
   * @see com.octopus.core.parser.Parser#parse(Response)
   */
  private Extras extras = new Extras();

  /**
   * 从http链接构建任务
   *
   * @param url 链接
   */
  private Request(String url) {
    this.url = url;
    this.userAgent(DEFAULT_USER_AGENT);
  }

  /**
   * 从http链接构建任务
   *
   * @param url http 链接
   * @return 爬虫任务
   */
  public static Request me(@NonNull String url) {
    return new Request((url));
  }

  /**
   * 设置爬虫任务链接
   *
   * @param url http 链接
   * @return 爬虫任务
   */
  public Request url(@NonNull String url) {
    this.url = url;
    return this;
  }

  /**
   * 获取任务http链接
   *
   * @return 当前任务http链接
   */
  public String url() {
    return this.url;
  }

  /**
   * 设置任务http method
   *
   * @param method http method
   * @return 爬虫任务
   */
  public Request method(@NonNull String method) {
    this.method = method;
    return this;
  }

  /**
   * 获取任务http method
   *
   * @return http method
   */
  public String method() {
    return this.method;
  }

  /**
   * 设置预期响应文本编码
   *
   * @param charset 文本编码
   * @return 爬虫任务
   */
  public Request charset(@NonNull String charset) {
    this.charset = charset;
    return this;
  }

  /**
   * 获取当前任务预期响应编码方式
   *
   * @return 编码方式
   */
  public String charset() {
    return this.charset;
  }

  /**
   * 设置当前任务是否可重复。默认：可重复
   *
   * @param repeatable 是否可重复
   * @return 爬虫任务
   */
  public Request repeatable(boolean repeatable) {
    this.repeatable = repeatable;
    return this;
  }

  /**
   * 判断当前任务是否可重复
   *
   * @return true（可重复），false（不可重复）
   */
  public boolean repeatable() {
    return this.repeatable;
  }

  /**
   * 设置当前任务的下载可重试次数
   *
   * @param max 重试次数
   * @return 当前任务
   */
  public Request maxRetryTimes(int max) {
    this.maxRetryTimes = max;
    return this;
  }

  /**
   * 获取当前任务的可重试次数
   *
   * @return 重试次数
   */
  public int maxRetryTimes() {
    return this.maxRetryTimes;
  }

  /** 重试次数增加 */
  public void fail() {
    this.retryTimes++;
  }

  /**
   * 获取当前任务重试次数
   *
   * @return 重试次数
   */
  public int retryTimes() {
    return this.retryTimes;
  }

  /**
   * 判断当前任务是否可重试
   *
   * @return true可重试，false不可重试
   */
  public boolean retriable() {
    return this.retryTimes < this.maxRetryTimes;
  }

  /**
   * 设置当前任务http header
   *
   * @param key header name
   * @param value header
   * @return 当前任务
   */
  public Request header(String key, String value) {
    this.headers.put(key, value);
    return this;
  }

  /**
   * 设置当前任务User-Agent头
   *
   * @param userAgent userAgent
   * @return 当前任务
   */
  public Request userAgent(String userAgent) {
    return this.header("User-Agent", userAgent);
  }

  /**
   * 设置当前任务Referer头
   *
   * @param referer referer
   * @return 当前任务
   */
  public Request referer(String referer) {
    return this.header("Referer", referer);
  }

  /**
   * 设置当前任务http header
   *
   * @param headers http header
   * @return 当前任务
   */
  public Request headers(@NonNull Map<String, String> headers) {
    this.headers.putAll(headers);
    return this;
  }

  /**
   * 获取任务http header
   *
   * @return http header
   */
  public Map<String, String> headers() {
    return this.headers;
  }

  /**
   * 设置http cookie
   *
   * @param key cookie name
   * @param value cookie
   * @return 当前任务
   */
  public Request cookie(String key, String value) {
    this.cookies.put(key, value);
    return this;
  }

  /**
   * 设置http cookie
   *
   * @param cookies http cookie
   * @return 当前任务
   */
  public Request cookies(@NonNull Map<String, String> cookies) {
    this.cookies.putAll(cookies);
    return this;
  }

  /**
   * 获取当前任务http cookie
   *
   * @return http cookie
   */
  public Map<String, String> cookies() {
    return this.cookies;
  }

  /**
   * 设置http下载超时时间（毫秒）
   *
   * @param readTimeout 超时时间
   * @return 当前任务
   */
  public Request readTimeout(int readTimeout) {
    this.readTimeout = readTimeout;
    return this;
  }

  /**
   * 获取下载超时时间
   *
   * @return 超时时间
   */
  public int readTimeout() {
    return this.readTimeout;
  }

  /**
   * 下载链接超时时间（毫秒）
   *
   * @param connectTimeout 超时时间
   * @return 当前任务
   */
  public Request connectTimeout(int connectTimeout) {
    this.connectTimeout = connectTimeout;
    return this;
  }

  /**
   * 获取当前下载链接超时时间
   *
   * @return 超时时间
   */
  public int connectTimeout() {
    return this.connectTimeout;
  }

  /**
   * 存储额外信息
   *
   * @param key 额外信息key
   * @param obj 信息内容
   * @return 当前任务
   */
  public Request extra(String key, Object obj) {
    this.extras.put(key, obj);
    return this;
  }

  /**
   * 获取所有的额外信息
   *
   * @return 所有的额外信息
   */
  public Extras extras() {
    return this.extras;
  }

  /**
   * 存储额外信息
   *
   * @param extras 额外信息
   * @return 当前任务
   */
  public Request extras(Map<String, Object> extras) {
    this.extras.put(extras);
    return this;
  }

  @Override
  public String toString() {
    return String.format(
        "Request[url = %s, retryTimes = %s, maxRetryTimes = %s]", url, retryTimes, maxRetryTimes);
  }
}
